/*
 * ndmp_xdr.c - The XDR routines (External data representaion) 
 * 
 * These are set of routines resposibile for encoding and decoding the data types to network order. NDMP uses XDR to represet data structures.
 * XDR is a part of ONC-RPC. Unfortunately, there is no proper RPC library in the kernel. The NFS libraries could not be used direct. 
 * The user land rpc library's memory allocataion is completely different in kernel space, so the user space RPC library can not be used either.
 * So a set of XDR functions were written just for this project.
 *
 *
 * Besides the basic XDR routine, the complex structures XDR routines can be generated automatically by rpcgen. Still some modifications are required.
 *
 *      Author: Aboo Valappil
 *      Date: 2/10/2008
 *      Based on: ONC-RPC XDR functions and functions generated by rpcgen
 */
#include "ndmp_main.h"


/* xdr_init - Initialize the XDR related data structures */
char xdr_init (XDR *xdrs, char op)
{

	if ( op == XDR_ENCODE ) xdrs->pos=4; 
	else xdrs->pos=0;

	xdrs->op=op;
	xdrs->kvcount=0;
	xdrs->kvlen=0;
	xdrs->pads=0;
	xdrs->kpages[0]=NULL;
	return TRUE;
}

/*  xdr_u_long - Encode and decode function for a unsigned long, a primitive type */	
char xdr_u_long (XDR *xdrs, u_long *i )
{
	int t;

	if ( xdrs->pos >= XDR_BUFFER_SIZE-4 ) return FALSE;
	if ( xdrs->op==XDR_ENCODE ) {
		t=htonl(*i);
		memcpy(xdrs->buffer+xdrs->pos, &t, 4 );
	}
	else { 
		memcpy(&t, xdrs->buffer+xdrs->pos,4);
		*i=ntohl(t);
	}
	xdrs->pos+=4;
	return TRUE;
}

/* Since the enumerated, short, and char types are encoded and decoded in the same way as u_long, just declare it like xdr_u_long */
#define xdr_enum xdr_u_long
#define xdr_short xdr_u_long
#define xdr_u_short xdr_u_long
#define xdr_u_char xdr_u_long

/*
 * xdr_string - encode/decode function for string, another primitive type 
 */
char xdr_string (XDR *xdrs, char **string)
{

	int l,t;
	if ( xdrs->op==XDR_ENCODE ) {
		l=strlen(*string);
		if ( l >= MAX_XDR_STRING_SIZE ) return FALSE;
		t=htonl(l);
		memcpy(xdrs->buffer+xdrs->pos,&t,4);
		xdrs->pos+=4;
		memcpy(xdrs->buffer+xdrs->pos,*string,l);
// This is to take care of the padding (The size of the XDR record must be in multiple of 4)
		if ( l % 4) l+=(4 - l%4);
		xdrs->pos+=l;
	}
	else {
		if ( ! xdr_u_long(xdrs,(u_long *)&l) ) 
			return FALSE;
		if ( l >= MAX_XDR_STRING_SIZE ) return FALSE;
// This is to take care of the padding (The size of the XDR record must be in multiple of 4)
		if ( l % 4) l+=(4 - l%4);
		xdrs->pos+=l;
	}
		
	return TRUE;
}

/* 
 * xdr_opaque - encode/decode for opaque type including arrays, another primitive encoding decoding function 
 */
char xdr_opaque (XDR *xdrs, char **string, int len)
{

	int t;
	if ( xdrs->op==XDR_ENCODE ) {
		if ( len >= MAX_XDR_STRING_SIZE ) return FALSE;
		t=htonl(len);
		memcpy(xdrs->buffer+xdrs->pos,&t,4);
		xdrs->pos+=4;
		memcpy(xdrs->buffer+xdrs->pos,*string,len);
// This is to take care of the padding (The size of the XDR record must be in multiple of 4)
		if ( len % 4) len+=(4 - len%4);
		xdrs->pos+=len;
	}
	else return FALSE; // There is no need for decoding opaque, only encode is used here. 

	return TRUE;
}

/* 
 * The following are the XDR function generated by rpcgen. The .x defenitions where taken from the ndmp.org website for NDMP version 4.
 * Some functions required manual modification (See the comments for them) as the memory allocation is done through the scatter gather 
 * buffers which is not a flat memory space and also, scatter gather buffers has no equivalent virtual address as it is supposed to be used for DMA
 */
char xdr_ndmp_header_message_type (XDR *xdrs, ndmp_header_message_type *objp)
{

         if (!xdr_enum (xdrs, (u_long*) objp))
                 return FALSE;
        return TRUE;
}

char xdr_ndmp_header (XDR *xdrs, ndmp_header *objp)
{

         if (!xdr_u_long (xdrs, &objp->sequence))
                 return FALSE;
         if (!xdr_u_long (xdrs, &objp->time_stamp))
                 return FALSE;
         if (!xdr_ndmp_header_message_type (xdrs, &objp->message_type))
                 return FALSE;
         if (!xdr_ndmp_message (xdrs, &objp->message_code))
                 return FALSE;
         if (!xdr_u_long (xdrs, &objp->reply_sequence))
                 return FALSE;
         if (!xdr_ndmp_error (xdrs, &objp->error_code))
                 return FALSE;
        return TRUE;
}

char xdr_ndmp_error (XDR *xdrs, ndmp_error *objp)
{

         if (!xdr_enum (xdrs, (u_long *) objp))
                 return FALSE;
        return TRUE;
}

char xdr_ndmp_message (XDR *xdrs, ndmp_message *objp)
{
         if (!xdr_enum (xdrs, (u_long *) objp))
                 return FALSE;
        return TRUE;
}

char xdr_ndmp_auth_type (XDR *xdrs, ndmp_auth_type *objp)
{

         if (!xdr_enum (xdrs, (u_long *) objp))
                 return FALSE;
        return TRUE;
}

char xdr_ndmp_auth_text (XDR *xdrs, ndmp_auth_text *objp)
{

         if (!xdr_string (xdrs, &objp->auth_id))
                 return FALSE;
         if (!xdr_string (xdrs, &objp->auth_password))
                 return FALSE;
        return TRUE;
}


char xdr_ndmp_auth_data (XDR *xdrs, ndmp_auth_data *objp)
{

         if (!xdr_ndmp_auth_type (xdrs, &objp->auth_type))
                 return FALSE;
        switch (objp->auth_type) {
           case NDMP_AUTH_NONE:
                break;
           case NDMP_AUTH_TEXT:
                 if (!xdr_ndmp_auth_text (xdrs, &objp->ndmp_auth_data_u.auth_text))
                         return FALSE;
                break;
           case NDMP_AUTH_MD5:
                         return FALSE;
                break;
           default:
                return FALSE;
        }
        return TRUE;
}

char xdr_ndmp_connect_client_auth_request (XDR *xdrs, ndmp_connect_client_auth_request *objp)
{

         if (!xdr_ndmp_auth_data (xdrs, &objp->auth_data))
                 return FALSE;
        return TRUE;
}

char xdr_ndmp_connect_client_auth_reply (XDR *xdrs, ndmp_connect_client_auth_reply *objp)
{
         if (!xdr_ndmp_error (xdrs, &objp->error))
                 return FALSE;
        return TRUE;
}

char xdr_ndmp_tape_open_request (XDR *xdrs, ndmp_tape_open_request *objp, char type)
{
         if (!xdr_string (xdrs, &objp->device))
                 return FALSE;
	if ( type == TAPE_DEVICE ) {
         	if (!xdr_u_long (xdrs, (u_long *)&objp->mode))
               	  return FALSE;
	}
        return TRUE;
}

char xdr_ndmp_tape_open_reply (XDR *xdrs, ndmp_tape_open_reply *objp)
{
         if (!xdr_ndmp_error (xdrs, &objp->error))
                 return FALSE;
        return TRUE;
}

char xdr_ndmp_scsi_close_reply (XDR *xdrs, ndmp_scsi_close_reply *objp)
{

         if (!xdr_ndmp_error (xdrs, &objp->error))
                 return FALSE;
        return TRUE;
}

/* This xdr routine needs modification from the function generated by rpcgen as the IO payload is scatter and gather buffer.
 * xdr_ndmp_sg will take care of the SG mappings
 */
char xdr_ndmp_execute_cdb_request (XDR *xdrs, ndmp_execute_cdb_request *objp, int sgcount)
{

		if ( xdrs->op == XDR_DECODE ) return FALSE;

		if (!xdr_u_long (xdrs, &objp->flags))
                                 return FALSE;
                if (!xdr_u_long (xdrs, &objp->timeout))
                                 return FALSE;
                if (!xdr_u_long (xdrs, &objp->datain_len))
				return FALSE;
                if (!xdr_opaque (xdrs, &objp->cdb.cdb_val,objp->cdb.cdb_len))
				return FALSE;
                if (!xdr_ndmp_sg (xdrs, (struct scatterlist *)objp->dataout.dataout_val,sgcount,&(objp->dataout.dataout_len)))
				return FALSE;

                return TRUE;
}

/* This xdr routine needs modification from the function generated by rpcgen as the IO payload is scatter and gather buffer.
 * xdr_ndmp_sg will take care of the SG mappings
 */
char xdr_ndmp_execute_cdb_reply (XDR *xdrs, ndmp_execute_cdb_reply *objp, int sgcount)
{

	if ( xdrs->op == XDR_ENCODE ) return FALSE;

         if (!xdr_ndmp_error (xdrs, &objp->error))
                 return FALSE;
         if (!xdr_u_char (xdrs, (u_long *)&objp->status))
                 return FALSE;
         if (!xdr_u_long (xdrs, &objp->dataout_len))
                 return FALSE;
         if (!xdr_ndmp_sg (xdrs, (struct scatterlist *)objp->datain.datain_val,sgcount,&(objp->datain.datain_len)))
                 return FALSE;
        return TRUE;
}

char xdr_ndmp_notify_connection_status_post (XDR *xdrs, ndmp_notify_connection_status_post *objp)
{

         if (!xdr_ndmp_connection_status_reason (xdrs, &objp->reason))
                 return FALSE;
         if (!xdr_u_short (xdrs, (u_long *)&objp->protocol_version))
                 return FALSE;
         if (!xdr_string (xdrs, &objp->text_reason))
                 return FALSE;

        return TRUE;
}

char xdr_ndmp_connection_status_reason (XDR *xdrs, ndmp_connection_status_reason *objp)
{

         if (!xdr_enum (xdrs, (u_long *) objp))
                 return FALSE;
        return TRUE;
}

/*
 * xdr_ndmp_sg - This is required to map the scatter gather buffer to kernel virtual address and then to IO vector to be used with kernel_rcvmsg
 */
char xdr_ndmp_sg (XDR *xdrs, struct scatterlist *sg, int sgcount,int *len )
{

        int i;
        void *address;
        void *kaddr;
        int l=0;
	int s1;
	int sglen;
	int sgoffset;
	struct page *sgpage;

	struct page **kpages;

	xdrs->kvcount=0;
	xdrs->kvlen=0;



        if (!xdr_u_long (xdrs, (u_long *)len))
                  return FALSE;

	if ( ! *len  )  goto last;

	kpages = xdrs->kpages;

// If no sgcount passwd, it is a direct buffer (not scatter gather buffer). Just add the buffer to io vector
        if ( ! sgcount ) {
                xdrs->kv[xdrs->kvcount].iov_base=( void *)sg;
                xdrs->kv[xdrs->kvcount].iov_len=*len;
                xdrs->kvcount++;
		xdrs->kvlen=*len;
		l=*len;
        }
        else {
			// Map every sg buffer to kernel virtual address
                        for(i=0;i<sgcount;i++) {

                                if ( i >= MAX_KV_COUNT  ) {
                                        return FALSE;
                                }

				sglen=sg[i].length;
				sgpage=sg_page(sg+i);
				sgoffset=sg[i].offset;
//				printk("SG page: %x, length: %x, offset: %x\n", sgpage, sglen, sgoffset);
			

				// This loops is required to make sure that every page (4K pages) are individually mapped to kernel virtual address
                                // Some SG buffers are larger than 4KB 
				while ( ( *len - l ) && sglen ) {
                               		kaddr=kmap(sgpage);
                                	if ( ! kaddr ) return FALSE;
					*kpages=sgpage;
					kpages++;
                                	address=kaddr+sgoffset;
                               		xdrs->kv[xdrs->kvcount].iov_base=address;

					// Take care of the buffers larger than PAGE_SIZE
					if ( sglen > PAGE_SIZE ) {
						xdrs->kv[xdrs->kvcount].iov_len=PAGE_SIZE-sgoffset;
						sglen-=(PAGE_SIZE-sgoffset);
						sgpage++;
					} else {
						xdrs->kv[xdrs->kvcount].iov_len=sglen;
						sglen=0;
					}

					// Some times the IO payload returned is much less than requested size, take care of that situation here
					if ( l+xdrs->kv[xdrs->kvcount].iov_len > *len ) {
						xdrs->kv[xdrs->kvcount].iov_len=*len-l;
						i=sgcount;
						sglen=0;
					}

//					printk("SG split: sgpage: %x, sglength: %x, sgoffset: %x\n", sgpage, sglen, sgoffset);

                                	l+=xdrs->kv[xdrs->kvcount].iov_len;
					xdrs->kvlen=l;
                                	xdrs->kvcount++;

					sgoffset=0;
				} 
                        }
			*kpages=NULL;
        }
// This is to take care of the padding done by XDR encode (The size of the XDR record must be in multiple of 4)
	s1= 4 - ( xdrs->kvlen % 4 );
	if ( s1 != 4 ) {
		xdrs->kv[xdrs->kvcount].iov_base=xdrs->buffer+xdrs->pos;
		xdrs->kv[xdrs->kvcount].iov_len=s1;
		xdrs->kvcount++;
		xdrs->kvlen+=s1;
		xdrs->pads=s1;
	}

	xdrs->residual-=l;

last:

        return TRUE;
}
